package com.jsftoolkit.gen.writer.impl;

import java.io.PrintStream;
import java.lang.reflect.Modifier;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Set;

import javax.el.ValueExpression;
import javax.faces.component.UIComponent;

import com.jsftoolkit.base.TagBase;
import com.jsftoolkit.gen.writer.ClassWriter;
import com.jsftoolkit.gen.writer.ClassWriterWrapper;
import com.jsftoolkit.gen.writer.TagWriter;

/**
 * This implementation wraps a {@link ClassWriter} and defers most of the heavy
 * lifting (writing) to it.
 * 
 * @author noah
 * 
 */
public class TagWriterImpl extends ClassWriterWrapper implements TagWriter {

	private Class<?> valueBindingClass = ValueExpression.class;

	private String componentClass;

	private String _package;

	private Set<String> fields = new LinkedHashSet<String>();

	/**
	 * Starts wrapping nothing. You'll need to call
	 * {@link #setWriter(ClassWriter)}.
	 */
	public TagWriterImpl() {
		super();
	}

	/**
	 * Wraps a {@link BasicClassWriter}.
	 * 
	 * @param out
	 *            the stream to write to.
	 */
	public TagWriterImpl(PrintStream out) {
		this();
		setWriter(new BasicClassWriter(out));
	}

	@Override
	public void printPackage(String _package) {
		super.printPackage(_package);
		this._package = _package;
	}

	/**
	 * Adds imports necessary for the code generated by this implementation.
	 */
	@Override
	public void printImports(Set<Class<?>> imports) {
		imports.add(getValueBindingClass());
		imports.add(UIComponent.class);
		imports.add(TagBase.class);

		super.printImports(imports);
	}

	public void printComponentImport(String componentPackage,
			String componentClass) {
		this.componentClass = componentClass;
		if (!_package.equals(componentPackage) && componentClass != null
				&& componentPackage != null) {			
			printf("import %s.%s;\n\n", componentPackage, componentClass);
		}
	}

	public void printAttributeField(String name) {
		startField(Modifier.PRIVATE, getValueBindingClass(), getFieldName(name));
		fields.add(name);
		println(";\n");
	}

	@SuppressWarnings("serial")
	public void startSetProperties() {
		startMethod(Modifier.PROTECTED, void.class, "setProperties",
				new LinkedHashMap<String, Class<?>>() {
					{
						put("component", UIComponent.class);
					}
				});
		indent();
		print("super.setProperties(component);");
	}

	public void printPropertySet(String name, String key) {
		print('\n');
		indent();
		if (componentClass == null || key == null) {
			printf("setProperty(component, \"%s\", %s);", name,
					getFieldName(name));
		} else {
			printf("setProperty(component, %s.%s, %s);", componentClass, key,
					getFieldName(name));
		}

	}

	@SuppressWarnings("serial")
	public void printAccessors() {
		for (final String attrib : fields) {
			printPropertyAccessors(attrib, getValueBindingClass());
		}
	}

	public void printTypeAndRendererGetters(String type, String rendererType) {
		startMethod(Modifier.PUBLIC, String.class, "getComponentType", null);
		indent().print("return ");
		if (componentClass != null) {
			printf("%s.COMPONENT_TYPE", componentClass);
		} else {
			printString(type);
		}
		print(';').endBlock();

		startMethod(Modifier.PUBLIC, String.class, "getRendererType", null);
		indent().print("return ");
		if (componentClass != null) {
			printf("%s.DEFAULT_RENDERER_TYPE", componentClass);
		} else {
			printString(rendererType);
		}
		print(';').endBlock();
	}

	@Override
	@SuppressWarnings("serial")
	public void close() {
		startMethod(Modifier.PROTECTED, void.class, "setProperty",
				new LinkedHashMap<String, Class<?>>() {
					{
						put("component", UIComponent.class);
						put("attributeName", String.class);
						put("valueExpression", getValueBindingClass());
					}
				});
		indent().printType(TagBase.class);
		print(".setProperty(component, attributeName, valueExpression);")
				.endBlock();

		super.close();
	}

	private void printString(String s) {
		if (s == null) {
			print("null");
		} else {
			printf("\"%s\"", s);
		}
	}

	/**
	 * 
	 * @return the class to use for value bindings/expressions. JSF 1.2 should
	 *         use {@link ValueExpression}, though 1.1's
	 *         {@link javax.faces.ValueBinding} can be used for backwards
	 *         compatability.
	 */
	public Class<?> getValueBindingClass() {
		return valueBindingClass;
	}

	public void setValueBindingClass(Class<?> vbClass) {
		this.valueBindingClass = vbClass;
	}

}
